package com.zm.springmvc;

import com.zm.springmvc.util.Utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.rmi.RemoteException;
import java.util.Enumeration;
import java.util.Map;

public class MyDispatcherServlet extends HttpServlet {
    WebApplicationContext context;

    public MyDispatcherServlet(WebApplicationContext context) {
        this.context = context;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //查找控制器方法
        Handler handler = handlerMapping(req);
        Object ret = "测试路径解析";
        if (handler==null){
            //静态资源处理
            int status = DefaultServletHandling(req, resp);
            if (status == 404){
                //返回404页面
            }else {
                return;
            }
        }else {
            //执行控制器方法
            ret = handler.handle(req,resp);
        }

        //响应结果
        Controller controller = handler.getContorller().getClass().getAnnotation(Controller.class);
//        RestController restController = handler.getContorller().getClass().getAnnotation(RestController.class);
        if (controller !=null) {
            RequestBody requestBody = handler.getMethod().getAnnotation(RequestBody.class);
            if (requestBody == null){
                //执行页面跳转
                String viewName;
                if(ret instanceof String){
                    //字符串视图名：  forward：... or redirect:...
                    viewName = ret.toString();
                }else if(ret instanceof ModelAndView){
                    viewName = ((ModelAndView) ret).getViewName();
                }else {
                    throw new RemoteException("返回结果类型错误！");
                }
                if(viewName.startsWith("forward:")){
                    throw new RemoteException("请求转发未实现！");
                }
                viewName = viewName.substring("redirect:".length());
                //响应重定向
                resp.sendRedirect(viewName);
                return;
            }
        }else {
            if(ret instanceof ModelAndView){
                String viewName = ((ModelAndView) ret).getViewName();
                viewName = viewName.substring("redirect:".length());
                //响应重定向
                resp.sendRedirect(viewName);
                return;
            }
        }

        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().println(ret);
    }

    //处理静态资源
    private int DefaultServletHandling(HttpServletRequest req, HttpServletResponse resp) {
        //读取静态资源的路径
//        http://localhost/String_Mvc_war_exploded/body2
        String path = req.getServletPath();// /body2
        String uri = req.getRequestURI();// /String_Mvc_war_exploded/body2

        System.out.println("path = " + path);
        System.out.println("uri = " + uri);
        //获取资源磁盘路径
        String realPath = this.getServletContext().getRealPath(path);
        System.out.println("realPath = " + realPath);

        File file = new File(realPath);
        if (file.exists() == false) {
            return 404;
        }

        if (file.getName().endsWith(".html")) {
            resp.setContentType("text/html;charset=utf-8");
        }else if (file.getName().endsWith(".css")) {
            resp.setContentType("text/css;charset=utf-8");
        }else if (file.getName().endsWith(".js")) {
            resp.setContentType("application/javascript;charset=utf-8");
        }

        //读取静态资源
        try(FileInputStream fis = new FileInputStream(realPath)){
            ServletOutputStream out = resp.getOutputStream();
            byte[] bytes = new byte[1024];
            int count;
            while ((count = fis.read(bytes))>-1){
                //返回浏览器
                out.write(bytes,0,count);
            }
        }catch (Exception e){
            throw new RuntimeException("读取文件错误",e);
        }

       return 200;
    }

    private Handler handlerMapping(HttpServletRequest req) {
        Handler ret = null;
        Map<String, Object> controllerMap = context.getBeansWithAnnotation(Controller.class);
        Map<String, Object> restControllerMap = context.getBeansWithAnnotation(RestController.class);
        System.out.println("controllerMap = " + controllerMap);
        System.out.println("restControllerMap = " + restControllerMap);
        controllerMap.putAll(restControllerMap);

        //根据地址查找控制器方法
        String uri = req.getRequestURI();
        System.out.println("uri = " + uri);
        for (Object controller : controllerMap.values()) {
            RequestMapping classRequestMapping = controller.getClass().getAnnotation(RequestMapping.class);
            for (Method declaredMethod : controller.getClass().getDeclaredMethods()) {
                RequestMapping methodRequestMapping = declaredMethod.getAnnotation(RequestMapping.class);
                String addr ;

                if(methodRequestMapping==null){
                    System.out.println("当前方法是用的GetMapping PostMapping");
                    continue;
                }

                if(methodRequestMapping.value().length>0){
                    addr= methodRequestMapping.value()[0];
                }else {
                    addr=methodRequestMapping.path()[0];
                }

                if (classRequestMapping!=null){
                    if(classRequestMapping.value().length>0){
                        addr= classRequestMapping.value()[0] + "/"+ addr;
                    }else {
                        addr=classRequestMapping.path()[0] + "/"+ addr;
                    }
                }
                String contextPath = this.getServletContext().getContextPath();
                addr = contextPath + (addr.startsWith("/") ? "" : "/") + addr;
                System.out.println("addr = " + addr);
                if(addr.contains("{")){
                    //路径参数
                }else if(addr.contains("?") || addr.contains("*")){
                    //TODO 模块匹配 => 正则表达式
                }else {
                    if (uri.equals(addr)){
                        //构建handler
                        ret = new Handler(controller,declaredMethod);
                    }
                }
            }
        }
        return ret;
    }

    //生命周期方法

    @Override
    public void init() throws ServletException {
        //手动加载bean
        System.out.println("手动加载bean...");
        ((AnnotationConfigWebApplicationContext)this.context).refresh();
        super.init();
    }
}
@Data
@AllArgsConstructor
class Handler{
    Object contorller;
    Method method;
    public Object handle(HttpServletRequest req, HttpServletResponse resp) {
        Object ret = null;
        //获取方法参数 => 获取方法参数名
        method.setAccessible(true);
        if (method.getParameterCount()==0) {
            //无方法参数
            try {
                 ret = method.invoke(contorller);
            } catch (Exception e) {
                throw new RuntimeException("执行控制器方法错误",e);
            }
        }else{
            //有方法参数
//            for (Parameter parameter : method.getParameters()) {
//                System.out.println("parameter.getName() = " + parameter.getName());
//            }
            //获取方法参数名数组
            Parameter[] parameters = method.getParameters();
            String[] parameterNames = Utils.getParameterNames(method);
            Object[] parameterValues = new Object[parameterNames.length];
            for (int i = 0; i < parameters.length; i++) {
                String parameterName = parameterNames[i];
//                parameters[i].getClass()  //错误Parameter.class
                String svalue = req.getParameter(parameterName);
                Object ovalue = null;
                Class<?> type = parameters[i].getType();    //真正的参数类型 如String.class
                if(String.class.isAssignableFrom(type)){
                    ovalue = svalue;
                }else if(int.class.isAssignableFrom(type)||Integer.class.isAssignableFrom(type)){
                    ovalue = Integer.valueOf(svalue);
                }else if(HttpServletRequest.class.isAssignableFrom(type)){
                    ovalue = req;
                }else if(HttpServletResponse.class.isAssignableFrom(type)){
                    ovalue = resp;
                }else if(HttpSession.class.isAssignableFrom(type)){
                    ovalue = req.getSession();
                }else if(InputStream.class.isAssignableFrom(type)){
                    try {
                        ovalue = req.getInputStream();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }else if(OutputStream.class.isAssignableFrom(type)){
//                    try {
//                        ovalue = resp.getOutputStream();
//                    } catch (IOException e) {
//                        throw new RuntimeException(e);
//                    }
                }else if(Person.class.isAssignableFrom(type)){
                    //自动装箱
                    try {
                        Object person = type.newInstance();
                        //获取每个参数设置到person中
                        Enumeration<String> names = req.getParameterNames();
                        while (names.hasMoreElements()){
                            String name = names.nextElement();
                            System.out.println("name = " + name);
                            String value = req.getParameter(name);
                            System.out.println("value = " + value);
                            //TODO
                            Utils.setValue(person,name,value);
                        }
                        ovalue = person;
                    } catch (Exception e) {
                        throw new RuntimeException("执行控制器方法错误",e);
                    }

                }else if(ModelAndView.class.isAssignableFrom(type)){
                   ovalue = new ModelAndView();
                }else {
                    //TODO 其他类型待拓展  Object ModelAndView。。。。
                }
                parameterValues[i] = ovalue;
            }
            try {
                ret = method.invoke(contorller,parameterValues);
            } catch (Exception e) {
                throw new RuntimeException("执行控制器方法错误",e);
            }

        }
        return ret;
    }
}